package com.dz.finance.common.service.impl;

import com.dz.finance.common.constant.SystemConstant;
import com.dz.finance.common.entity.BaseUserInfo;
import com.dz.finance.common.exception.BizException;
import com.dz.finance.common.service.AuthorService;
import com.dz.finance.common.service.UserTokenService;
import com.dz.finance.common.config.SecurityConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.HandlerExceptionResolver;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author: DengHaoQi
 * @date: 2024/6/10 20:36
 * @description: 鉴权过滤器，过滤器执行完后才到Controller
 */

@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "sys", name = "enable-my-security", havingValue = "true")
public class AuthorServiceImpl<T> implements AuthorService<T> {


    private final SecurityConfig securityConfig;
    private final UserTokenService<T> userTokenService;
    private final RedisTemplate<String, Object> redisTemplate;
    private final AntPathMatcher antPathMatcher;
    private HandlerExceptionResolver handlerExceptionResolver;

    /*
     *
     * */
    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            // 未开启安全配置则直接放行
            if (Objects.isNull(securityConfig) || !securityConfig.getEnable()) {
                filterChain.doFilter(request, response);
                return;
            }

            T user = null;
            // 从token中获取用户
            if ("token".equals(securityConfig.getGetUserType())) {
                // 从请求头中获取token
                String token = request.getHeader("api-access-token");
                user = userTokenService.checkToken(token);
            }

            // gateway：从网关传递的请求头user中获取(后续可能会拓展的功能)
            if ("user".equals(securityConfig.getGetUserType())) {
                // 从请求头中获取token
                String token = request.getHeader("user");
                user = userTokenService.checkToken(token);
            }

            Assert.notNull(user, "无法获取到用户信息");
            BaseUserInfo baseUser = (BaseUserInfo) user;

            // 检查当前用户是否有权限能访问当前路径
            boolean isPermit = checkPermissions(baseUser.getSysRoleIds(), request.getServletPath());
            if (!isPermit) {
                throw new BizException("非法访问");
            }

            // 将用户信息存储在ThreadLocal中
            userTokenService.setThreadLocalUser(user);
            filterChain.doFilter(request, response);
            // 防止内存泄漏？
            userTokenService.removeThreadLocalUser();
        } catch (Exception e) {
            // 请求还未到Controller, 需要调用此方法将异常给全局异常处理器，否则无法捕获
            handlerExceptionResolver.resolveException(request,response,null,e);
        }
    }


    /*
    * 白名单
    * */
    @Override
    public boolean shouldNotFilter(HttpServletRequest request) {
        if (securityConfig == null || !securityConfig.getEnable() || CollectionUtils.isEmpty(securityConfig.getIgnores())) {
            return false;
        }
        String path = request.getServletPath();
        boolean ignore = securityConfig.getIgnores().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
        if (log.isDebugEnabled()) {
            log.info("path: {} [ignore: {}]", path, ignore);
        }
        return ignore;
    }


    /*
     * 权限校验
     * true:  有权限
     * false: 无权限
     * */
    private boolean checkPermissions(Set<Long> sysRoleIds, String servletPath) {
        // 管理员拥有所有权限
        if (sysRoleIds.contains(SystemConstant.ROLE_ADMIN)) {
            return true;
        }
        Set<String> menuIdList = getMenuIdByCache(sysRoleIds);
        if (Objects.isNull(menuIdList) || CollectionUtils.isEmpty(menuIdList)) {
            throw new BizException("角色没有绑定任何资源");
        }
        for (String path : menuIdList) {
            if (antPathMatcher.match(path,servletPath)) {
                return true;
            }
        }
        return false;
    }


    /*
     * 从缓存中获取角色对应的菜单id
     * */
    private Set<String> getMenuIdByCache(Set<Long> sysRoleIds) {
        HashOperations<String, String, Set<String>> hashOperations = redisTemplate.opsForHash();
        List<Set<String>> multiGet = hashOperations.multiGet(SystemConstant.ROLE_RESOURCE_PERMISSIONS,
                sysRoleIds.stream().map(String::valueOf).collect(Collectors.toList()));
        return multiGet.stream()
                .filter(p -> !CollectionUtils.isEmpty(p))
                .flatMap(Set::stream)
                .collect(Collectors.toSet());
    }
}
